import copy
import json
from typing import Any, List, Optional, Literal, Union

from pydantic import BaseModel, Field, constr, validator

operation_dicts = {"equal": "=", "like": "like", "in": "in"}

link_dicts = {"and": "must", "or": "should", "not": "must_not"}


class EsQueryDetail(BaseModel):
    link: Literal['and', 'or', 'not']
    field_name: Union[str, List[str]]
    operation: Literal['equal', 'like', 'in']
    field_value: Any
    index_type: int = 0  # 0为只有keyword索引，1为只有分词索引（text），2为同时有keyword索引和分词索引

    @validator("link")
    def validate_link_values(cls, value):
        allowed_values = ('and', 'or', 'not')
        if value not in allowed_values:
            raise ValueError(f"link 不允许值 {allowed_values} 只允许 {str(allowed_values)}")
        return value

    @validator("operation")
    def validate_operation_values(cls, value):
        allowed_values = ('equal', 'like', 'in')
        if value not in allowed_values:
            raise ValueError(f"operation 不允许值 {allowed_values} 只允许 {str(allowed_values)}")
        return value

    def field_name_sqlstr(self):
        # like 需要前后模糊
        feild_value = self.field_value
        if self.operation == "like":
            feild_value = "%{}%".format(self.field_value)
        temp_string = ""
        for field_name in self.field_name:
            temp_string = temp_string + "%s %s '%s' or " % (field_name, self.operation, feild_value)
        temp_string = "(" + temp_string.rstrip(" or ") + ")"
        if self.link == "not":
            temp_string = "not " + temp_string
        return temp_string

    def field_name_list(self):
        if self.operation == "like":
            query = "match"
        else:
            query = "term"
        dsl_tmp = {"bool": {"should": []}}
        for field_name in self.field_name:
            dicts_or = {}
            if query == "term" and self.index_type == 2:
                field_name = field_name + ".keyword"
            if query == "match":
                dicts_or[query] = {field_name: {"query": self.field_value, "fuzziness": "1"}}
                dsl_tmp["bool"]["should"].append({"match_phrase": {field_name: self.field_value}})
            else:
                dicts_or[query] = {field_name: self.field_value}
            dsl_tmp["bool"]["should"].append(dicts_or)
        if self.link == "not":
            dsl_tmp = {"bool": {"must_not": [copy.deepcopy(dsl_tmp)]}}
        sql_str = self.field_name_sqlstr()
        return dsl_tmp, sql_str

    def operation_in(self):
        tmp = {"terms": {self.field_name: self.field_value}}
        if self.link == "not":
            sql_str = " ".join([self.field_name, "not in", str(tuple(self.field_value))])
            dsl_tmp = {"bool": {"must_not": [tmp]}}
        else:
            sql_str = " ".join([self.field_name, "in", str(tuple(self.field_value))])
            dsl_tmp = tmp
        return dsl_tmp, sql_str

    def normal_dsl(self):
        dsl_tmp = {"bool": {}}
        if self.operation == "like":
            query = "match"
            query_sql_str = "LIKE"
        else:
            query = "term"
            query_sql_str = "="
        field_name = self.field_name
        if query == "term" and self.index_type == 2:
            field_name = self.field_name + ".keyword"

        if query == "match":
            dsl_tmp["bool"]["should"] = [
                {query: {field_name: {"query": self.field_value, "fuzziness": "1"}}},
                {"match_phrase": {field_name: self.field_value}}
            ]
        else:
            dsl_tmp = {query: {field_name: self.field_value}}

        field_value = self.field_value
        if self.operation == "like":
            field_value = "%{}%".format(self.field_value)

        sql_str = "%s %s '%s'" % (self.field_name, query_sql_str, field_value)

        if self.link == "not":
            if query_sql_str == "LIKE":
                query_sql_str = "NOT LIKE"
            else:
                query_sql_str = "!="
            sql_str = "%s %s '%s'" % (self.field_name, query_sql_str, field_value)
            dsl_tmp = {"bool": {"must_not": [copy.deepcopy(dsl_tmp)]}}

        return dsl_tmp, sql_str

    def get_query_dsl(self) -> tuple:
        if isinstance(self.field_name, list):
            dsl_tmp, sql_str = self.field_name_list()
        elif isinstance(self.field_value, list):
            dsl_tmp, sql_str = self.operation_in()
        else:
            dsl_tmp, sql_str = self.normal_dsl()
        return dsl_tmp, sql_str


class EsQueryRangeDetail(BaseModel):
    link: Literal['and', 'or', 'not']
    field_name: str
    operation1: Literal['gt', 'gte', 'lt', 'lte']  # gt gte lt lte 大于 大于等于 小于 小于等于
    field_value1: Any
    operation2: Optional[Literal['gt', 'gte', 'lt', 'lte']] = None
    field_value2: Optional[Any] = None

    @validator("link")
    def validate_link_values(cls, value):
        allowed_values = ('and', 'or', 'not')
        if value not in allowed_values:
            raise ValueError(f"link 不允许值 {allowed_values} 只允许 {str(allowed_values)}")
        return value

    @validator("operation1")
    def validate_operation1_values(cls, value):
        allowed_values = ('gt', 'gte', 'lt', 'lte')
        if value not in allowed_values:
            raise ValueError(f"operation1 不允许值 {allowed_values} 只允许 {str(allowed_values)}")
        return value

    @validator("operation2")
    def validate_operation2_values(cls, value):
        allowed_values = ('gt', 'gte', 'lt', 'lte')
        if value is None:
            return value
        if value not in allowed_values:
            raise ValueError(f"operation2 不允许值 {allowed_values} 只允许 {str(allowed_values)}")
        return value

    def get_query_dsl(self) -> tuple:
        # 这种写法在两个值都有的情况下比较好
        # return {"range": {self.field_name: {self.operation1: self.field_value1,
        #                                     self.operation2: self.field_value2}}}
        include_lower, include_upper = True, False
        if self.operation1 in ["gt", "gte"]:
            from_value = self.field_value1
            to_value = self.field_value2
            if self.operation1 == "gte":
                include_lower = True
            else:
                include_lower = False
            if self.operation2 and self.operation2 == "lte":
                include_upper = True
            else:
                include_upper = False

        else:
            from_value = self.field_value2
            to_value = self.field_value1
            if self.operation1 == "lte":
                include_upper = True
            else:
                include_upper = False
            if self.operation2 and self.operation2 == "gte":
                include_lower = True
            else:
                include_lower = False

        if include_lower:
            include_lower_str = "<="
        else:
            include_lower_str = "<"
        if include_upper:
            include_upper_str = ">="
        else:
            include_upper_str = ">"

        if self.operation2:
            dsl = {
                "range": {
                    self.field_name: {
                        "from": from_value,
                        "to": to_value,
                        "include_lower": include_lower,
                        "include_upper": include_upper
                    }
                }
            }
            sql_str = " ".join([from_value, include_lower_str, self.field_name, include_upper_str, to_value])
            return dsl, sql_str
        else:
            if self.operation1 in ["lt", "lte"]:
                dsl = {"range": {self.field_name: {"to": to_value, "include_upper": include_upper}}}
                sql_str = " ".join([self.field_name, include_upper_str, to_value])
                return dsl, sql_str
            else:
                dsl = {"range": {self.field_name: {"from": from_value, "include_lower": include_lower}}}
                sql_str = " ".join([self.field_name, include_lower_str, from_value])
                return dsl, sql_str


class EsQuerySchema(BaseModel):
    query: List[Union[EsQueryDetail, EsQueryRangeDetail]] = Field(...)
    size: Optional[int] = Field(default=10000, gt=0, lte=10000)
    from_start: Optional[int] = Field(default=0, gt=0, lte=10000)
    source_field: Optional[List[str]]

    def get_dsl(self):
        dsl = {
            "size": self.size,
            "from": self.from_start
        }
        # dsl["_source"] = self.source_field

        all_dsl, all_sql_str = self.deal_query()
        dsl["query"] = all_dsl

        dsl = json.dumps(dsl, ensure_ascii=False, indent=2)
        return dsl, all_sql_str

    def deal_query(self):
        i = 1
        all_sql_str = ""
        all_dsl_list = []
        new_dsl_list = []

        for detail in self.query:
            dsl_tmp, sql_str = detail.get_query_dsl()
            if i == 1:
                all_sql_str = sql_str
                new_dsl_list.append(dsl_tmp)
            else:
                if detail.link in ['not', 'and']:
                    all_sql_str = all_sql_str + " and " + sql_str
                    new_dsl_list.append(dsl_tmp)
                else:
                    all_dsl_list.append(new_dsl_list)
                    new_dsl_list = []
                    all_sql_str = all_sql_str + " or " + sql_str
                    new_dsl_list.append(dsl_tmp)
            i = i + 1
        all_dsl_list.append(new_dsl_list)
        if len(all_dsl_list) == 1:
            all_dsl = {"bool": {"must": all_dsl_list[0]}}
        else:
            all_dsl = {"bool": {"should": []}}
            for item in all_dsl_list:
                if len(item) == 1:
                    all_dsl["bool"]["should"].extend(item)
                else:
                    all_dsl["bool"]["should"].append({"bool": {"must": item}})
        return all_dsl, all_sql_str
